Page Links
Getting Started with the PiGlow Module

Overview

These instructions are intended to be a guide for getting started with the PiGlow module created by Pimoroni.

The first task of any programming course is the creation of a "Hello World" program. If you are looking to control hardware directly, the second task is typically flashing an LED (Light Emitting Diode). That's essentially what we will be doing here, except that the piglow module has 18 LEDs, and we will do a little more than just turn them on and off. While it may seem like flashing some lights doesn't serve any real purpose, this is a great way to test the Python installation, make sure that low-level hardware resources like I2C (smbus) are configured properly, and to get acquainted with some useful tools. Plus, every project looks better with flashing lights!

If you haven't already made the Raspberry Pi "Project Ready", please do that first.


What is the PiGlow Module?

The PiGlow module is a small add-on board that plugs into the J1 connector of the Raspberry Pi. At the heart of this module is an SN3218 18-channel LED driver, which we will control through the I2C bus interface. The brightness of each LED is individually controlled by a PWM (Pulse Width Modulation) output that adjusts the average current of each LED. The PWM values range from 0 (OFF) to 255 (Full Brightness). The hardware has been configured such that the maximum current to an LED set at full brightness is 23mA. With all LEDs set to full brightness simultaneously, the total current draw would be 415mA.

The PiGlow module is shown below installed on a model B Raspberry Pi with all lights lit simultaneously to show the various colors and how they are configured. There are three spiral arms, each of which contains the colors (white, blue, green, yellow, orange, red), in that order, starting from the center of the spiral arm, moving outward.

Piglow module installed on Pi

The design documents for the piglow module are available here:


Example Code

The piglow-tutorial.py Python script looks like this:

#!/usr/bin/env python
#***********************************************************************
# piglow-tutorial.py
#
# This file was created for DavesMotleyProjects.com
#
# Once running you'll need to press ctrl-C to cancel stop the script
#
# Information about the piglow hardware can be found at the pimoroni
# piglow GitHub repo at: https://github.com/pimoroni/piglow 
#
# This software is provided under the following conditions: 
#
# Permission is hereby granted, free of charge, to any person obtaining 
# a copy of this software and associated documentation files (the 
# 'Software'), to deal in the Software without restriction, including 
# without limitation the rights to use, copy, modify, merge, publish, 
# distribute, sublicense, and/or sell copies of the Software, and to 
# permit persons to whom the Software is furnished to do so, subject to 
# the following conditions:
# 
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#
#***********************************************************************

import time
from smbus import SMBus
import curses


#***********************************************************************
# Initialize the curses library
# curses provides a simple method to create display input/output with
# standard python libraries. This isn't as fancy as using PyGame, but
# it gets the job done without requiring the user to load anything 
# extra. The commands below set desired behaviors for this application. 
#***********************************************************************

stdscr = curses.initscr()	# returns a window object for the screen	
curses.noecho()				# turns of automatic echoing of keys
curses.cbreak()			# react to keys without needing Enter pressed
curses.curs_set(0)		# do not display a blinking cursor 
stdscr.keypad(1)		# allows curses to replace multi-byte sequences 
stdscr.nodelay(1)		# tell curses not to wait for input


#***********************************************************************
# Constants
#***********************************************************************

# addresses for the SN3218 IC used in the PiGlow module
CMD_ENABLE_OUTPUT = 0x00
CMD_ENABLE_LEDS = 0x13
CMD_SET_PWM_VALUES = 0x01
CMD_UPDATE = 0x16

# constant values used by this script
ON = 1
OFF = 0
FORWARD = 0
BACKWARD = 1
ORIGINAL = 0
MODIFIED = 1

# this position list is used to convert the pattern to be displayed
# into the positions in which they are connected to the SN3218 IC. 
# this allows us to create patterns in a more predictable way, like
# this: [White1, Blue1, Green1, Yellow1, Orange1, Red1, 
#		 White2, Blue2, Green2, Yellow2, Orange2, Red2, 
#		 White3, Blue3, Green3, Yellow3, Orange3, Red3]

position = [12,  14,  3,  2,  1, 0, 
			10, 11, 13, 15, 16,  17, 
			9,  4,  5,  8,  7, 6]

# this correction factor list adjusts the brightness of each of the 
# colors to be more equivalent. Without this, the white LEDs are so
# bright that they overwhelm everything, and hurt the eyes. 

correction = [15, 60, 45, 100, 90, 90, 
		      15, 60, 45, 100, 90, 90, 
	          15, 60, 45, 100, 90, 90]


#***********************************************************************
# Initialize variables
#***********************************************************************

# Not required in this instance. This will be accomplished by
# set_whirly_vortex which is called at the end of this file.  


#***********************************************************************
# PiGlow Class
#***********************************************************************

class PiGlow:
	bus = None
	i2c_addr = 0x54 # fixed i2c address of SN3218 ic

	# this function is called when the class is instantiated (created)
	def __init__(self, i2c_bus=1):
		self.bus = SMBus(i2c_bus)

	# first we tell the SN3218 to enable output (turn on)
		self.write_i2c(CMD_ENABLE_OUTPUT, [0x01])

	# then we ask it to enable each bank of LEDs (0-5, 6-11, 12-17)
		self.write_i2c(CMD_ENABLE_LEDS, [0xFF, 0xFF, 0xFF])

	# this function is called when it is time to update the pattern 
	def update_leds(self):
		
		# create local list variables
		adj_pattern = list(pattern)
		new_values = list(pattern)
		
		# loop through from 0 to the length of the pattern array			
		for i in range(len(adj_pattern)):
			
			# if the brightness adjust is on...
			if (adjust == ON):
				# and the mode is not 'whirly-vortex' (mode 1)...
				if (mode != 1):
					# adjust the brightness of the colors based on the 
					# correction array. This helps the colors appear 
					# more equal. Otherwise white is crazy bright!
					adj_pattern[i] = adj_pattern[i] \
					* correction[i] / 100
				# else, if the mode is 'whirly-vortex', then the 
				# correction array must be adjusted based on the
				# original led positioning. 
				else:
					adj_pattern[position[i]] = \
					adj_pattern[position[i]] * correction[i] / 100
				
			# in all cases adjust the brightness based on the global
			# brightness value. This adjusts all LEDs at once. 
			adj_pattern[i] = adj_pattern[i] * brightness / 255
		
		# loop through from 0 to the length of the pattern array	
		for i in range(len(adj_pattern)):
			
			# if the desired order is the new (obvious) ordering...
			if ordering == MODIFIED:
				# re-adjust order of new_values based on position array
				new_values[position[i]] = adj_pattern[i]
			# for the original ordering (based on board layout)
			else:
				# just copy the current current pattern
				new_values[i] = adj_pattern[i]
		
		
		self.write_i2c(CMD_SET_PWM_VALUES, new_values)
		self.write_i2c(CMD_UPDATE, [0xFF])

	# write_i2c is a helper that writes the given value or list of 
	# values to the SN3218 IC over the i2c protocol
	def write_i2c(self, reg_addr, value):
		self.bus.write_i2c_block_data(self.i2c_addr, reg_addr, value)


# create an instance of the PiGlow class on I2C Bus 1
piglow = PiGlow(1)


#***********************************************************************
# Screen Value Update Functions
# These functions update the user interface values to reflect the 
# currently selected values. Call these functions after modifying any
# of the variables to update the user interface. By centralizing these
# update functions, it is not necessary to remember the screen locations
# in several areas of the code, making it easier to adjust the location
# of screen contents. 
#***********************************************************************

# a function to update all on-screen values at once
def update_all(screen):
	update_mode(screen)
	update_brightness(screen)
	update_direction(screen)
	update_speed(screen)
	update_ordering(screen)
	update_adjust(screen)

# update the mode value on the screen
def update_mode(screen):
	if mode == 1:
		stdscr.addstr(16,9,"Whirly-Vortex");
	elif mode == 2:
		stdscr.addstr(16,9,"Galaxy-Vortex");
	elif mode == 3:	
		stdscr.addstr(16,9,"Spiral-Vortex");
	elif mode == 4:
		stdscr.addstr(16,9,"ALL LEDS ON  ");

# update the brightness value on the screen
def update_brightness(screen):
	stdscr.addstr(16,42,"          ");
	stdscr.addstr(16,42,str(brightness));

# update the direction value on the screen
def update_direction(screen):
	if direction == BACKWARD:
		stdscr.addstr(17,14,"Backward");
	else:	
		stdscr.addstr(17,14,"Forward ");

# update the speed value on the screen	
def update_speed(screen):
	stdscr.addstr(17,37,"          ");
	stdscr.addstr(17,37,str(speed));	

# update the led ordering value on the screen
def update_ordering(screen):
	if ordering == ORIGINAL:
		stdscr.addstr(18,13,"Original  ");
	else:	
		stdscr.addstr(18,13,"Modified  ");

# update the 'global brightness adjust value on the screen	
def update_adjust(screen):
	if (adjust == ON):
		stdscr.addstr(18,49,"On ");
	else:	
		stdscr.addstr(18,49,"Off");
				

#***********************************************************************
# Functions to change the pattern
# These functions set the piglow module up to present the major modes:
# whirly, galaxy, and spiral vortex. These functions modify several 
# variables at once to create the preset condition. 
#***********************************************************************

def set_whirly_vortex(screen):
	global mode, speed, direction, brightness, adjust, ordering, pattern
	mode = 1
	speed = 0.02
	adjust = ON
	brightness = 200
	ordering = ORIGINAL
	direction = FORWARD
	# Whirly-Vortex pattern (old ordering)
	pattern = [0x01,0x02,0x04,0x08,0x10,0x18,
		   0x20,0x30,0x40,0x50,0x60,0x70,
		   0x80,0x90,0xA0,0xC0,0xE0,0xFF]
	update_all(screen)
	stdscr.refresh();	       
	       
def set_galaxy_vortex(screen):
	global mode, speed, direction, brightness, adjust, ordering, pattern
	mode = 2
	speed = 0.10
	adjust = ON
	brightness = 200
	ordering = MODIFIED
	direction = FORWARD
	# Galaxy-Vortex pattern (with new ordering)
	pattern = [0x00,0x10,0x20,0x40,0x80,0xFF,
		   0x00,0x10,0x20,0x40,0x80,0xFF,
		   0x00,0x10,0x20,0x40,0x80,0xFF]
	update_all(screen)
	stdscr.refresh();

def set_spiral_vortex(screen):
	global mode, speed, direction, brightness, adjust, ordering, pattern
	mode = 3
	speed = 0.04
	adjust = ON
	brightness = 200
	ordering = MODIFIED
	direction = FORWARD
	# Spiral-Vortex pattern (with new ordering)
	pattern = [0x00,0x00,0x00,0x00,0x00,0x00,
		   0x00,0x00,0x00,0x00,0x00,0x00,
		   0x00,0x00,0x00,0xFF,0xFF,0xFF]
	update_all(screen)
	stdscr.refresh();

# This is a 'hidden' mode that is not presented on the user interface. 
# It isn't very exciting, but it is useful for taking photographs of
# the piglow board. Good for you for taking a look 'under the hood' to
# see how this worked. Now can you figure out how to turn it on?
def set_all_on(screen):
	global mode, speed, direction, brightness, adjust, ordering, pattern
	mode = 4
	speed = 0
	adjust = ON
	brightness = 10
	ordering = MODIFIED
	direction = FORWARD
	# Turn all LEDs on (hidden mode)
	# This makes taking nice pictures of the board much easier!
	pattern = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]
	update_all(screen)
	stdscr.refresh();


#***********************************************************************
# the main() function
#***********************************************************************

# the variable 'screen' is passed only because of the use of the 
# curses.wrapper(main) function, which unrolls adjustments to the 
# command-line in the event of a program crash during debugging, 
# which can leave the command-line terminal in an unusable state. 
def main(screen):

	
	global mode, speed, direction, brightness, adjust, ordering, pattern

	####################################################################
	# Setup main display
    # This section of code executes one time, and sets up the static
    # portion of the user interface. Once written, these sections will
    # not need to be updated (meaning they are not overwritten by 
    # anything during execution of the script.

	for i in range(79):
		stdscr.addstr(0,i,"*"); stdscr.addstr(21,i,"*");
	for i in range(21):
		stdscr.addstr(i,0,"*"); stdscr.addstr(i,79,"*");

	stdscr.addstr(2,3, "Piglow Example from DavesMotleyProjects.com");
	stdscr.addstr(4,3, "Press '1' for the Whirly-Vortex");
	stdscr.addstr(5,3, "Press '2' for the Galaxy-Vortex");
	stdscr.addstr(6,3, "Press '3' for the Spiral-Vortex");
	stdscr.addstr(7,3, "Press UP_ARROW: to increase brightness");
	stdscr.addstr(8,3, "Press DN_ARROW: to decrease brightness");
	stdscr.addstr(9,3, "Press RT_ARROW: to increase speed");
	stdscr.addstr(10,3, "Press LT_ARROW: to decrease speed");
	stdscr.addstr(11,3,"Press 'R' to Reverse direction");
	stdscr.addstr(12,3,"Press 'O' to toggle Ordering");
	stdscr.addstr(13,3,"Press 'B' to toggle Brightness adjust");

	stdscr.addstr(16,3,"Mode: ");
	stdscr.addstr(16,30,"Brightness: ");
	stdscr.addstr(17,3,"Direction: ");
	stdscr.addstr(17,30,"Speed: ");
	stdscr.addstr(18,3,"Ordering: ");
	stdscr.addstr(18,30,"Brightness adjust: ");

	update_all(screen)
		
	stdscr.addstr(20,3,"Press 'Ctrl-C': to Exit");
	
	stdscr.refresh();

	# try: means... do this until something goes wrong. Since there is
    # no global except: in this case, execution will simply pass to the
    # code beyond the try: block if something goes wrong.  
	try:
		
		# loop forever
		while True:

            ############################################################
            # Continuously update the piglow module
            ############################################################
            
			# if the direction is forwards...
			if direction == FORWARD:
				# pop the last value off the array
				# and place it at index 0 (first position)	
				pattern.insert(0,pattern.pop(-1))
			# if the direction is backwards...
			else:
				# pop the first value off the array
				# and place in the last position
				pattern.insert(len(pattern),pattern.pop(0))					

			# after the pattern is updated above...
			# update the piglow module with current values
			piglow.update_leds()
	
			# sleep for a bit. This determines the speed at which the
            # pattern updates. 
			time.sleep(speed)

			############################################################
            # Check for user inputs, and update settings
            ############################################################
            
            # clear the key variable, and then collect the user input
            # using getch(). Because we previously set the nodelay(1)
            # option during initialization, curses will not wait for 
            # user input. If there is no user input ready, execution 
            # will simply move on.
			key = ''
			key = stdscr.getch()

            # if the user presses the UP arrow key...
            # brightness will be increased by 1 for values below 10, 
            # by 5 up to 55, and by 50 beyond that, up to a max of 255. 
			if key == curses.KEY_UP:
				if brightness <= 9.5:
					brightness+=1	
				elif brightness <= 54.5:
					brightness+=5
				else: 
					brightness+=50
				if brightness > 255:
					brightness = 255
				update_brightness(screen)
				stdscr.refresh();

            # if the user presses the DOWN arrow key...
            # brightness will be decreased by 1 for values below 10, 
            # by 5 below to 55, and by 50 below 255, down to 0. 
			elif key == curses.KEY_DOWN:
				if brightness <= 10.5:
					brightness-=1
				elif brightness <= 55.5:
					brightness-=5
				else: 
					brightness-=55
				if brightness < 0:
					brightness = 0
				update_brightness(screen)
				stdscr.refresh();

            # if the user presses the RIGHT arrow key...
            # the speed will be increased (by decreasing the delay)
            # the delay is decreased by 0.001 for delay values below 
            # 0.01, and by 0.01 otherwise, to a min of 0.
			elif key == curses.KEY_RIGHT:
				if speed <= 0.015:
					speed -= 0.001
				else: 
					speed -= 0.01
				if speed < 0.0001:
					speed = 0
				update_speed(screen)
				stdscr.refresh();

            # if the user presses the LEFT arrow key...
            # the speed will be decreased (by increasing the delay)
            # the delay is increased by 0.001 for delay values below 
            # 0.01, and by 0.01 otherwise, to a max of 1.0.
			elif key == curses.KEY_LEFT:
				if speed <= 0.0095:
					speed += 0.001
				else: 
					speed += 0.01
				if speed > 1:
					speed = 1
				update_speed(screen)
				stdscr.refresh();

            # if the user presses the 'r' key on the keyboard...
            # the direction of the pattern updating will be revresed.
			elif key == (ord('r') or ord('R')):
				if direction == BACKWARD:
					direction = FORWARD
				else:	
					direction = BACKWARD
				update_direction(screen)
				stdscr.refresh();

            # if the user presses the 'o' key on the keyboard...
            # the ordering of the leds will revert between the 
            # original order (as they are layed out on the board), and
            # the modified order, which makes the ascending bits in the
            # pattern corrsepond to ascending bits up each arm of the 
            # led spiral pattern. 
			elif key == (ord('o') or ord('O')):
				if ordering == ORIGINAL:
					ordering = MODIFIED
				else:	
					ordering = ORIGINAL
				update_ordering(screen)
				stdscr.refresh();
				
            # if the user presses the 'b' key on the keyboard...
            # the brightness adjustment will be toggled on or off. The
            # adjustment state is ignored for the whirly-vortex mode.
            # When enabled, this will adjust the brightness of each
            # color, so that they more closely match each other in
            # perceived intensity. 
			elif key == (ord('b') or ord('B')):
				if adjust == ON:
					adjust = OFF
				else:	
					adjust = ON
				update_adjust(screen)
				stdscr.refresh();
			
            
            # If the user selects the numbers 1 through 4...
            # the mode will be changed to initialize a new pattern
            # including initializing to new default values of speed
            # brightness, ordering, etc. 
			
			elif key == ord('1'):
				set_whirly_vortex(screen)

			elif key == ord('2'):
				set_galaxy_vortex(screen)

			elif key == ord('3'):
				set_spiral_vortex(screen)
            
			elif key == ord('4'):
				set_all_on(screen)

    # this section executes in the event that Ctrl+C is pressed...
	except KeyboardInterrupt:
		# set all LEDs to "off" before exiting
		pattern = [0x00,0x00,0x00,0x00,0x00,0x00,
		   0x00,0x00,0x00,0x00,0x00,0x00,
		   0x00,0x00,0x00,0x00,0x00,0x00]
		piglow.update_leds()
        # gracefully unwind the curses changes to the terminal
		curses.endwin()

	return 0

#***********************************************************************
# The point at which things actually happen!
# nothing above actually does anything until it is called. The commands
# below do the calling that kicks everything off.  
#***********************************************************************

# initiaize the variable to display the whirly-vortex on startup
set_whirly_vortex(stdscr)

# instead of just calling main, use the curses.wrapper to call main. 
# this way, if the program crashes, the wrapper will handle unwinding
# the changes made by curses to the terminal window before exiting. 
# without this the terminal could be left in an unusable state. 
curses.wrapper(main)


#***********************************************************************
# END OF FILE
#***********************************************************************



Running the Tutorial Code

I have created a Python script to help demonstrate some cool patterns that can be made using the Piglow module. The Python script also uses some interesting Python features, such as the curses library for creating a simple UI (User Interface). I have also added LED re-ordering, brightness adjust, and a few other interesting features. The point being to make it entertaining enough to hold someone's attention, and to provide enough example code, that adapting the script for other projects is hopefully made easier. I hope you enjoy it!

Create a folder called piglow, by entering

mkdir piglow

Change to the directory by entering:

cd piglow

Download the piglow-tutorial.py python script file, by entering the following in a terminal window.

wget http:www.davesmotleyprojects.com/raspi/raspi-piglow-module/piglow-tutorial.py

Now launch the tutorial by entering:

sudo python piglow-tutorial.py

Note: sudo is required in order to allow python to access the I2C bus, which is a restricted resource.

When the application is launched you will see the following user interface displayed in a terminal window. (Color is inverted to make printing less expensive.)

Piglow tutorial user display

And the following pattern will be running on the piglow module.

whirly-vortex animation

The pattern being displayed is the whirly-vortex from the sample file that is available at the piglow GitHub repository. This is the pattern that is provided by the makers of the piglow module. I decided to make this the default pattern because 1) at the right speed and brightness it makes an interesting pattern, and 2) because it exposes an interesting truth in creating hardware modules. Due to the layout constraints of a two-sided PCB (Printed Circuit Board) the ordering of the pins isn't exactly optimal. The actual order of the LEDs is as shown below.

Original Pin assignments

If any other pattern, other than whirly-vortex is chosen by pressing '2' or '3' a "modified" LED ordering is used instead. This is accomplished by using a position[] list to re-order the natural layout to something that makes more sense (at least to me). The new order looks like this.

Modified Pin assignments

Pressing the '2' key on the keyboard loads the 'galaxy-vortex' pattern, which looks like this:

galaxy-vortex animation

Pressing the '3' key on the keyboard loads the 'spiral-vortex' pattern, which looks like this:

spiral-vortex animation

Go ahead and play with the various options available on the user interface. After all, this is a tutorial. See what happens.

Pressing the 'R' key reverses the direction of the pattern. The effect should be immediately obvious. Pressing the 'O' key toggles between the original LED ordering, and the modified LED ordering. Try it. Without the consistent symmetrical flow of the updated ordering, a pattern like the galaxy-vortex doesn't work. Pressing the 'B' key toggles the brightness adjust. With the adjust off, every LED is treated the same. This means that for something like the spiral-vortex, that each LED will be driven with the same current as the pattern moves around the spiral arms. If you also turn the brightness way up (using the UP_ARROW key) you should notice that the white LEDs are way brighter than the others. (I wouldn't stare directly at them if I were you) The brightness adjust varies the applied current based on the natural brightness of each LED color type. If you press '4', which is a hidden mode that lights all the LEDs for taking pictures of the board, you should see that all the LEDs appear to be at the same brightness.

I would recommend opening up the piglow-tutorial.py file. Try making modifications, and see what happens. If you find a mistake, or even better if you find a way to improve something I've done, let me know so that I can improve the tutorial for everyone else.